Conversation
8257880 to
913b508
Compare
|
|
|
Claude finished @mullapudipruthvik's task —— View job Code Review for TSS Message Signing Pending Approval ChangesTodo List:
Overall Assessment ✅This is a well-architected solution that systematically addresses the message signing policy flow issues. The implementation follows existing patterns and maintains backward compatibility while fixing critical user experience problems. Detailed Review🟢 Strengths
🟡 Minor Observations
🟢 Security Assessment
🟢 Code Quality
Recommendation: APPROVE ✅This PR successfully addresses the stated problems with a clean, well-tested solution that maintains architectural consistency. The implementation will significantly improve the user experience for message signing with policies enabled. |
| txHash: string; | ||
| signature: string; | ||
| // Absent when the sign request was parked behind a pending approval (see pendingApprovalId). | ||
| txHash?: string; |
There was a problem hiding this comment.
this is a breaking change. Please document it as such.
There was a problem hiding this comment.
Addressed in f10bf80 — and gone further than documentation: SignedMessage is now a discriminated union on the wallet-platform txRequest state:
type SignedMessage =
| { state: 'pendingApproval'; pendingApprovalId: string; txRequestId; messageRaw; … }
| { state: 'delivered'; txHash: string; signature: string; txRequestId; messageRaw; … };txHash/signature are required on the delivered variant, so consumers narrow on state rather than handling string | undefined — the compile error now lands exactly where an unguarded parked-result read would have misbehaved. BREAKING is documented on the union. Consumers in bitgo-ui (#12219) and retail-web (#10305) were updated in the same pass to narrow on state.
| txHash: string; | ||
| signature: string; | ||
| // Absent when the sign request was parked behind a pending approval (see pendingApprovalId). | ||
| txHash?: string; |
| * absent, and the caller should surface the approval instead of treating | ||
| * this as a failure. After approval, re-sign via signAndSendMessageTxRequest. | ||
| */ | ||
| pendingApprovalId?: string; |
There was a problem hiding this comment.
Why are we relying on this instead of the txRequest state? The txRequest state should clearly say what stage the txRequest is in, you shouldn't have to infer this from the presence of an id..
There was a problem hiding this comment.
Fair point — implemented in f10bf80: the discriminator is now the txRequest state itself. state: 'pendingApproval' | 'delivered' (message requests go pendingApproval → pendingDelivery → delivered; the txRequest-level signed state never occurs for message requests, so it is deliberately not part of the union). Detection was already state-based internally (isPendingApprovalTxRequestFull: apiVersion === 'full' && state === 'pendingApproval'); now the surfaced contract carries the state too, so no consumer has to infer anything from id presence.
| } | ||
|
|
||
| /** | ||
| * Signs (and, for full requests, delivers) the message of a message-sign |
There was a problem hiding this comment.
(and, for full requests, delivers)
why are we still supporting txRequest lite for this flow? Please make this flow full only, lite is a deprecated flow, it shouldn't be receiving new features.
There was a problem hiding this comment.
Good catch — the implementation never had a lite branch (message-sign requests are full-only; the deprecated lite flow never carries messages). The wording was inherited from signAndSendTxRequest's doc and was doubly misleading ('delivers' was also wrong — the method only signs; delivery completes server-side once signature shares are ingested). Doc comment rewritten in f10bf80 to state full-only explicitly.
913b508 to
f10bf80
Compare
The message-signing policy (DEFI-653) parks signMessage/signTypedStructuredData txRequests behind a pending approval, but the SDK's message-sign paths never inspected the txRequest state: they ran all MPC signing rounds against a parked request and failed at the final send with a wrapped 'Expected transaction request to be in state pendingDelivery' error, leaving the UI unable to recover the pending approval id. After approval, the only retry path (signAndSendTxRequest) signs through the transaction path and crashes on message-only requests (transactions[0].unsignedTx of undefined). - signMessageTss/signTypedDataTss: after create/fetch, return the pendingApprovalId (with txRequestId) when the txRequest is parked behind a pending approval instead of attempting to sign, mirroring the tx flow's early return in prebuildAndSignTransaction - SignedMessage: txHash/signature are now optional and a pendingApprovalId field marks the parked result; callers should treat a present pendingApprovalId as awaiting-approval data, not a failure - new signAndSendMessageTxRequest({ txRequestId, walletPassphrase }): signs a message txRequest through the message path (WP-persisted messageEncoded as bufferToSign), for resuming a parked request after approval; resolves with the pendingApprovalId if the request is still parked - rethrow the original signing errors instead of wrapping them in 'failed to sign message/typed data ...' so downstream callers can classify ApiResponseError results (e.g. the TxRequestPendingApprovalError 409) - sdk-coin-xdc: signXdcKycMessage fails loudly when the KYC sign request is parked behind a pending approval instead of returning an unusable signature-less result Refs: DEFI-1014
f10bf80 to
7d43929
Compare
Ticket
https://linear.app/bitgo/issue/DEFI-1014
Problem
The message-signing policy (DEFI-653) parks
signMessage/signTypedStructuredDatatxRequests behind a pending approval, but the SDK's message-sign paths never inspected the txRequest state:failed to sign typed data … Expected transaction request to be in state pendingDelivery but it is in state pendingApproval— the original E2E failure (Error 1). The string-wrap also destroyed theApiResponseErrorstructure, so no UI guard could recover the pending approval id.signAndSendTxRequest) signs through the transaction path and crashes on message-only requests:TypeError: Cannot read properties of undefined (reading 'unsignedTx')(Error 2).The tx-send flow already had the reference implementation:
prebuildAndSignTransactionearly-returns the PA-parked txRequest (isPendingApprovalTxRequestFull).Changes
signMessageTssandsignTypedDataTsscheck the created/fetched txRequest after create-or-fetch (both the fresh-create andtxRequestIdresume branches) and return{ coin, messageRaw, txRequestId, pendingApprovalId }instead of attempting MPC signing. Mirrors the tx flow's early return.SignedMessage = { state: 'pendingApproval'; pendingApprovalId; … } | { state: 'delivered'; txHash: string; signature: string; … }. The discriminator is the wallet-platform txRequest state (message requests gopendingApproval→pendingDelivery→delivered; the txRequest-levelsignedstate never occurs for messages). BREAKING: consumers previously readingtxHash/signatureunguarded must now narrow onstate === 'delivered'— the compile error lands exactly where an unguarded parked-result read would misbehave.signMessage/signTypedData/signAndSendMessageTxRequestall resolve parked requests to thependingApprovalvariant, so callers surface the approval instead of treating it as a failure. Callers in bitgo-ui (#12219) and retail-web (#10305) narrow onstatein the same pass.signAndSendMessageTxRequest({ txRequestId, walletPassphrase })— new public method; message-signing twin ofsignAndSendTxRequest. Signs a message txRequest through the message path (WP-persistedmessages[0].messageEncodedas bufferToSign) so the tx-request details page can resume a parked request after approval without theunsignedTxTypeError. Resolves withpendingApprovalIdif the request is still parked.signMessageTss/signTypedDataTssno longer wrap errors infailed to sign message/typed data …, soApiResponseErrorstructure (incl. the 409TxRequestPendingApprovalErrorcontract from DEFI-954 / WP PR 62988) survives for downstream guards.Test evidence
npx mocha test/v2/unit/wallet.ts --grep "Message Signing|Typed Data|policy flow"→ 48 passing (7 new):pendingApprovalId, no signing attemptedpendingApprovalId, no signing attemptedsignAndSendMessageTxRequeston apendingDeliverymessage request → signs, returnstxHash/signaturesignAndSendMessageTxRequeston a still-parked request →pendingApprovalId, no signing attemptedtest/v2/unit/wallet.tssuite → exit 0, byte-identical output to master baseline (incl. pre-existing Canton trace noise)tsc --noEmitin sdk-core → 0 errors; prettier cleanDependencies / ordering
Contract
SignedMessageconsumers:txHash/signaturearestring | undefinedafter this change — callers must checkpendingApprovalIdfirst (parked) or usetxHash/signature(signed). The no-policy path is unchanged: apendingDeliveryrequest signs exactly as before.